/*
 * Vencord, a Discord client mod
 * Copyright (c) 2025 Vendicated and contributors
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

import { PlainSettings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { chooseFile, saveFile } from "@utils/web";
import { moment, Toasts } from "@webpack/common";

import { DataStore } from "..";

type BackupType = "all" | "plugins" | "css" | "datastore";

const toast = (type: string, message: string) =>
    Toasts.show({
        type,
        message,
        id: Toasts.genId()
    });

const toastSuccess = () =>
    toast(Toasts.Type.SUCCESS, "Settings successfully imported. Restart to apply changes!");

const toastFailure = (err: any) =>
    toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`);

const logger = new Logger("SettingsSync:Offline", "#39b7e0");

function isSafeObject(obj: any) {
    if (obj == null || typeof obj !== "object") return true;

    for (const key in obj) {
        if (["__proto__", "constructor", "prototype"].includes(key)) {
            return false;
        }
        if (!isSafeObject(obj[key])) {
            return false;
        }
    }

    return true;
}

export async function importSettings(data: string, type: BackupType = "all", cloud = false) {
    try {
        var parsed = JSON.parse(data);
    } catch (err) {
        console.log(data);
        throw new Error("Failed to parse JSON: " + String(err));
    }

    if (!isSafeObject(parsed))
        throw new Error("Unsafe Settings");

    switch (type) {
        case "all": {
            if (!cloud && (!("settings" in parsed) || !("quickCss" in parsed) || !("dataStore" in parsed)))
                throw new Error("Invalid Settings. Missing one or more required keys.");

            if (parsed.settings) {
                Object.assign(PlainSettings, parsed.settings);
                await VencordNative.settings.set(parsed.settings);
            }
            if (parsed.quickCss) await VencordNative.quickCss.set(parsed.quickCss);
            if (parsed.dataStore) await DataStore.setMany(parsed.dataStore);
            break;
        }
        case "plugins": {
            if (!parsed.settings?.settings) throw new Error("Plugin settings missing");

            Object.assign(PlainSettings, parsed.settings.settings);
            await VencordNative.settings.set(parsed.settings.settings);
            break;
        }
        case "css": {
            if (!parsed.quickCss) throw new Error("CSS missing");

            await VencordNative.quickCss.set(parsed.quickCss);
            break;
        }
        case "datastore": {
            if (!parsed.dataStore) throw new Error("DataStore data missing");

            await DataStore.setMany(parsed.dataStore);
            break;
        }
    }
}

export async function exportSettings({ syncDataStore = true, type = "all", minify }: { syncDataStore?: boolean; type?: BackupType; minify?: boolean; }) {
    const settings = VencordNative.settings.get();
    const quickCss = await VencordNative.quickCss.get();
    const dataStore = syncDataStore ? await DataStore.entries() : undefined;

    switch (type) {
        case "all": {
            return JSON.stringify({ settings, quickCss, ...(syncDataStore && { dataStore }) }, null, minify ? undefined : 4);
        }
        case "plugins": {
            return JSON.stringify({ settings: { settings } }, null, minify ? undefined : 4);
        }
        case "css": {
            return JSON.stringify({ quickCss }, null, minify ? undefined : 4);
        }
        case "datastore": {
            return JSON.stringify({ dataStore }, null, minify ? undefined : 4);
        }
    }
}

export async function exportPlugins({ minify }: { minify?: boolean; } = {}) {
    const { plugins } = VencordNative.settings.get();
    return JSON.stringify({ settings: { plugins } }, null, minify ? undefined : 4);
}

export async function exportCSS({ minify }: { minify?: boolean; } = {}) {
    const quickCss = await VencordNative.quickCss.get();
    return JSON.stringify({ quickCss }, null, minify ? undefined : 4);
}

export async function exportDataStores({ minify }: { minify?: boolean; } = {}) {
    const dataStore = await DataStore.entries();
    return JSON.stringify({ dataStore }, null, minify ? undefined : 4);
}

export async function downloadSettingsBackup(type: BackupType = "all", { minify }: { minify?: boolean; } = {}) {
    const backup = await exportSettings({ minify, type });
    const filename = `equicord-${type}-backup-${moment().format("YYYY-MM-DD")}.json`;
    const data = new TextEncoder().encode(backup);

    if (IS_DISCORD_DESKTOP) {
        DiscordNative.fileManager.saveWithDialog(data, filename);
    } else {
        saveFile(new File([data], filename, { type: "application/json" }));
    }
}

export async function uploadSettingsBackup(type: BackupType = "all", showToast = true): Promise<void> {
    if (IS_DISCORD_DESKTOP) {
        const [file] = await DiscordNative.fileManager.openFiles({
            filters: [
                { name: "Vencord Settings Backup", extensions: ["json"] },
                { name: "all", extensions: ["*"] }
            ]
        });

        if (file) {
            try {
                await importSettings(new TextDecoder().decode(file.data), type);
                if (showToast) toastSuccess();
            } catch (err) {
                logger.error(err);
                if (showToast) toastFailure(err);
            }
        }
    } else {
        const file = await chooseFile("application/json");
        if (!file) return;

        const reader = new FileReader();
        reader.onload = async () => {
            try {
                await importSettings(reader.result as string, type);
                if (showToast) toastSuccess();
            } catch (err) {
                logger.error(err);
                if (showToast) toastFailure(err);
            }
        };
        reader.readAsText(file);
    }
}
